-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework PasswordlessClient
#45
Conversation
I think this looks good 👍 @justindbaur? |
This will make it harder/impossible to make a scoped variation. Part of the point of having the HttpClient be able to be per-configured is so others, mostly us, can add the ApiSecret on a per call basis based on a tenant. I'm fine with the other constructors for people without DI but when we have DI we should try and keep with the patterns exposed. We can make the constructor that expects the pre-configured |
Can you please share more details? I'm not entirely sure where the issue would arise. |
But IPasswordlessClient is also used in the ASP.NET Identity SDK, so by default injects the API secret from the options pattern (appsettings.json). When you're viewing an application in the AdminConsole, we call the back-end with API secrets from that specific application, so we need to override the secret. So that is what ScopedPasswordlessClient is doing. But debug through it when you're using the AdminConsole. We should probably come up with a solution where both use a different IHttpClientFactory implementation. Either using named or typed. And then do all the magic in DelegatingHandlers as it allows more flexibility to come up with different solutions such as what @justindbaur talks about. |
@jonashendrickx @justindbaur how about this? public class PasswordlessClientScope
{
public PasswordlessClient Client { get; }
public PasswordlessClientScope(HttpClient http, ICurrentContext context)
{
Client = new PasswordlessClient(
http,
new PasswordlessOptions(context.ApiKey, context.ApiSecret)
);
}
} You can make it implement Or, the direct equivalent, if you want: public class ScopedPasswordlessClient : PasswordlessClient
{
public ScopedPasswordlessClient(HttpClient http, ICurrentContext context)
: base(http, new PasswordlessOptions(context.ApiKey, context.ApiSecret)
{
}
} |
FWIW I think that looks less brittle than https://github.com/passwordless/passwordless-server/blob/main/src/AdminConsole/Services/ScopedPasswordlessClient.cs |
Yeah apologies, we can make this work with the scoped version it would just work differently than the original design of using a DelegatingHandler that I think @jonashendrickx had prototyped that I quite liked. IMO it's okay to trust that you are given objects that will do what they are built to do. Currently we abide by the typed client pattern because this library will primarily be used through DI in ASP.NET Core and this is the kind of stuff that pattern encourages. We also break the D in SOLID by creating a concretion in the constructor but we have already determined we are going to want to test this against a mock server so that matters a bit less. You all have the majority and will be working on this much more than I will be in the future, feel free to override me. |
@justindbaur You're not actively against this or consider the change harmful, right? As in we're discussing different tastes of the recipe, not adding arsenic to the meal? As long as there is no veto, I think this rework can go through, from my pov. |
I have unfortunately not seen that prototype.
The problem is that
That doesn't matter either way because we can still inject a fake HTTP handler inside the pipeline and substitute requests however we want to. So the concretion of |
No, this doesn't do any of the things I would reject it for which are
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright! No veto's heard but a good discussions of trade offs. I think the decreased surface area is a win in itself.
I'm still fine with making the constructor that does expect an HttpClient that is built for it less accessible. The current usage of that constructor does use one that was built for it.
That's not entirely true, it would still be possible for someone to create an HttpClient that is not compatible with our API. It could be disposed already, it could have to low of a timeout, this makes it harder to mess up but so does slapping |
Yeah, those are valid points, but I would argue that timeout/lifetime management are infrastructural/x-cutting concerns, while What I'm trying to say is that if you don't set Ultimately the difference in the two approaches comes down to this:
In any case, I appreciate everyone for challenging me and all the feedback on the PR 🙂 |
This is the changes proposed to
PasswordlessClient
in regards to itsHttpClient
management.High-level overview:
PasswordlessClient
now always creates its ownHttpClient
PasswordlessClient
sets the base URL and default headers on its ownHttpClient
PasswordlessClient
now owns the client it creates, it's safe to mutate its propertiesPasswordlessClient
'sHttpClient
uses a custom handler (that existed before this PR),PasswordlessHttpHandler
to wrap an externally providedHttpClient
with additional behaviorPasswordlessClient
, it also becomes the single source of truth for configurationAs a result, the whole request handling logic is now fully encapsulated within
PasswordlessClient
. In other words, if you create an instance ofPassworldlessClient
, you're guaranteed to have a fully working Passwordless client.If the user wants to use
IHttpClientFactory
without DI, they can use it like so (this replaces theCreate()
static method):If the user wants to use
IHttpClientFactory
with DI, this is possible in the same exact way as before:As a result, the library is pretty much completely decoupled from MS.E.DI, meaning that you can use it both in ASP.NET Core (or other DI-compatible environments) and in lower-level contexts, without any overhead.